#coding=utf-8

import hashlib
import os
from lxml import etree as ET
import time

def uuid(node):
    m = hashlib.md5()
    m.update(node)
    return m.hexdigest()

class PageManage(object):
    '''页面管理相关'''
    
    def __init__(self, driver, conf):
        self.driver = driver
        self.conf = conf
        self.page_list = {} # page_id, page
        self.current_page = None
        self.et = None
    
    def refresh_page(self): # 页面不在app中，返回fasle
        page_source = self.driver.get_current_page()
        self.et = ET.parse(page_source)
        if self._get_package() != self.conf.app:
            return False
        activity = self.driver.get_current_activity()
        page_object = self._wrapper_page(self.et, activity)
        self.__add_page(page_object)
        return True
    
    def _wrapper_page(self,tree, activity):
        return PageObject(tree, self.driver.width, self.driver.height, activity)
    
    def __add_page(self, page): # 添加页面时，覆盖前一个页面数据
        older_page = self.get_page(page.uuid)
        if older_page:
            page.set_attribute(older_page)
            self.page_list[page.uuid] = page
        else:
            self.page_list[page.uuid] = page
        self.current_page = page
    
    def get_page(self, uuid):
        return self.page_list.get(uuid)   
    
    def _get_package(self):
        ele = self.et.find('//*')
        return ele.get('package') 
    
    
class PageObject(object):
    '''页面对象，用于相关页面元素管理及统计'''
    
    def __init__(self, tree, width, height, activity):
        self.tree = tree
        self.width = width
        self.height = height
        self.package = self._get_package()
        self.activity = activity
        self.uuid = self._hash_id()
    
    def _hash_id(self): # 页面唯一值
        return uuid("-".join([self.activity] + self.__get_page_xpaths()))
    
    def __get_page_xpaths(self): # 用于参与计算页面唯一值
        eles = self.tree.xpath('//*')
        xpath_list = []
        for ele in eles:
            if ele.get('rotation'):
                continue
            xpath = self.tree.getelementpath(ele)
            xpath_list.append(xpath)
            ele.set('uuid',str(uuid(self.activity + xpath + ele.get('index'))))
        return xpath_list
    
    def _get_package(self):
        ele = self.tree.find('//*')
        return ele.get('package')
        
    def get_select_nodes(self, xpath_list):
        eles = []
        for xpath_str in xpath_list:
#             print xpath_str
            eles +=self._get_xpath_nodes(xpath_str)
        result = []
        index = []
        for ele in eles: # 去除重除的元素
            if ele.uuid not in index:
                index.append(ele.uuid)
                result.append(ele)
        return result
    
    def _get_xpath_nodes(self, xpath_str):
        '''通过xpath获取相关元素'''
        eles = self.tree.xpath(xpath_str.decode('utf-8'))
        return self.to_valid_element(eles)
    
    def is_contain_element(self,xpath_str):
        '''页面是否包含可选元素'''
        for xpath in xpath_str:
#             print xpath
            eles = self.tree.xpath(xpath.decode('utf-8'))
            if eles:
                return True
       
    def to_valid_element(self, eles):
        eles = filter(self._is_valid, eles)
        eles = filter(self._not_is_small, eles)
        return map(self._wrapper_element, eles)
    
    def get_clicked_nodes(self):
        clickeds = []
        eles = self._get_xpath_nodes("//*")
        for ele in eles:
            if ele.count > 0:
                clickeds.append(ele)
        return clickeds
    
    def _not_is_small(self, ele):
        return not self._is_small(ele)
         
    def _is_small(self, ele): # 过滤小元素
        if ele.get('bounds'):
            bound = Rect(eval(ele.get('bounds').replace("][",",")))
            if bound.endx >self.width or bound.endy > self.height:
                return True
            if bound.width < 30 or bound.height <30:
                return True
        return False
    
    def _is_valid(self, ele): # 计算元素是否有效
        if ele.get('bounds'):
            bound = Rect(eval(ele.get('bounds').replace("][",",")))
            return bound.is_display and ele.get('enabled')=='true'
        return False
    
    def _wrapper_element(self,ele): # 包装元素
        return Element(ele,self)

    def to_file(self, out_file=None):
        self.tree.write(out_file, encoding='utf-8', xml_declaration=True)
    
    def click(self, ele, report_dir):
        ele.set_count()
        self.to_file(os.path.join(report_dir, self.activity + "_" + self.uuid + ".xml"))  
    
    def set_attribute(self, page): # 同一页面，老页面数据初始化新页面数据
        eles = page.get_clicked_nodes()
        for ele in eles:
            if ele.uuid and ele.count:
                tmp = self._get_xpath_nodes("//*[@uuid='%s']"%ele.uuid)
                if len(tmp) > 0:
                    tmp[0].set_count(ele.count)   
    
class Element(object):
    
    def __init__(self, ele, page):
        self.ele = ele
        self.page = page
        self.activity = page.activity
        self.bound = tuple(eval(ele.get('bounds').replace("][",",")))
        self.uuid = self.ele.get('uuid')
#         self.ele.set('uuid', self.uuid)
        self.count = int(self.ele.get('count',str(0)))
        
    def get_url(self):
        return "-".join([self.activity, self.ele.get('class'), self.ele.get('index'),
                self.ele.get('resource-id'),self.ele.get('package'),self.ele.get('text')
            ])
    
    def get_screenshot_file_name(self):
        return "-".join([self.activity,self.uuid,str(time.time())])
    
    def center(self):
        return Rect(self.bound).center()
    
    def set_count(self, count=None):
        if count:
            self.count = count
        else:
            self.count += 1
        self.ele.set('count', str(self.count))
    
class Rect(object):
    
    def __init__(self, bound):
        self.x = bound[0]
        self.y = bound[1]
        self.endx = bound[2]
        self.endy = bound[3]
    
    def contain(self, rect):
        return self.endx >rect.endx or self.endy > rect.endy
    
    def center(self):
        return ((self.endx+self.x)/2,(self.endy+self.y)/2)
    
    def __str__(self):
        return "position(x:%s,y:%s,endx:%s,endy:%s)"%(self.x,self.y,self.endx,self.endy)
    
    @property
    def is_display(self):
        return self.endx > 0 and self.endy > 0
    
    @property
    def width(self):
        return self.endx - self.x
    
    @property
    def height(self):
        return self.endy - self.y